package ftp;
import java.lang.*;
import java.io.*;
import java.net.*;
import java.util.*;
import ftp.*;

/**
 * 封装FTP控制流。
 * 简单的代码示例请参考此文件最后。
 * @author 廖龙@CS0602
 */
public class CoreFtp {
    public boolean debug;
    /**
     * Tcp连接 - 指令流
     */
    Socket serverSocket;
    /**
     * 用户名,默认为anonymous
     */
    String username;
    /**
     * 密码，默认为anonymous
     */
    String password;
    /**
     * 远端服务器地址
     * 可以为debian.ustc.edu.cn之类的主机名，或者IP
     */
    String host;
    /**
     * 端口号。默认为21
     */
    int port;
    /**
     * 描述当前FTP连接的状态
     * false: 	停止状态
     * true:	已连接
     */
    boolean connected;
    /**
     * SOCKET内容日志
     */
    String log;
    /**
     * 0: PORT主动模式
     * 1: PASV被动模式
     */
    int mode;

    /**
     * 与服务器交互的字符流
     */
    DataOutputStream outToServer;
    BufferedReader inFromServer;

    /**
     * 构造函数
     */
    public CoreFtp(){
        this("debian.ustc.edu.cn", 21);
    }
    public CoreFtp(String newHost, int newPort){
	this(newHost, newPort, "anonymous", "anonymous");
    }
    public CoreFtp(String newHost, int newPort, String newUsername, String newPassword){
	host = newHost;
	port = newPort;
	username = newUsername;
	password = newPassword;
	connected = false;
	mode = 1;
	debug = false;
    }

    /**设置用户名*/
    public boolean setUsername(String newName){
        username = newName;
	return true;
    }
    /**设置密码*/
    public boolean setPassword(String newPassword){
	password = newPassword;
	return true;
    }
    /**设置远端服务器主机地址*/
    public boolean setHost(String newHost){
	host = newHost;
	return true;
    }
    /**设置端口*/
    public boolean setPort(int newPort){
	port = newPort;
	return true;
    }
    /**设置传输模式*/
    public boolean setMode(int newMode){
	if (newMode != 0) mode = 1;
	else mode = 0;
	return true;
    }
    public String getUsername(){	return username;    }

    public String getPassword() {	return password;    }

    public String getHost(){		return host;    }

    public int getPort(){		return port;    }

    public int getMode(){		return mode;    }

    /**@return 当前连接的状态*/
    public boolean isConnected(){	return connected;    }

    /**@return 本次连接中的指令流日志*/
    public String getLog(){		return log;    }

    /**@return 到服务器的Socket连接对象*/
    public Socket getSocket() throws Exception{
	if (connected =false) connect();
	return serverSocket;
    }
    private synchronized void setSocket() throws IOException{
        serverSocket = new Socket(host, port);
    }
    /**
     * 登录至FTP服务器。
     * 如果在没有登录的情况下进行更高级的操作（如列出目录、下载），会自动进行登录
     * @return
     * 	true:	登录成功
     * 	false:	登录失败
     */
    public synchronized boolean connect() throws IOException{
	//一些初始化操作
	if (debug) System.out.println("Getting socket...");
	setSocket();
	log = "";
	outToServer = new DataOutputStream(serverSocket.getOutputStream());
	inFromServer = new BufferedReader(new InputStreamReader(serverSocket.getInputStream()));
	//读取信息，判断服务器是否欢迎登录
	String response = read();
    while (response.startsWith("220-"))
        do{
            response = read();
        }while (response.startsWith("   "));
    
	if (!response.startsWith("220 "))
	    return false;
	//发送用户名
	response = send("USER " + username);
	if ( !response.startsWith("331 "))
	    return false;
	//发送密码
	response = send("PASS " + password);
	if (!response.startsWith("230 "))
	    return false;
	connected = true;
	ascii();
	return true;
    }
    public synchronized boolean connect(String name, String pass) throws IOException{
	setUsername(name);
	setPassword(pass);
	return connect();
    }
    /**
     * 断开连接
     */
    public synchronized void disconnect() throws IOException{
	try{
	    send("QUIT");
	}
	finally{
            serverSocket.close();
    	    serverSocket = null;
            connected = false;
	}
    }
    /**
     * 进入bin二进制数据传输模式。
     */
    public synchronized boolean bin() throws IOException {
	String response = send("TYPE I");
	return (response.startsWith("200 "));
    }

    /**
     * 进入ASCII文本字符数据传输模式。这是默认的模式。
     */
    public synchronized boolean ascii() throws IOException {
	String response = send("TYPE A");
	return (response.startsWith("200 "));
    }
    /**
     * 将FTP的连接切换至path路径。可以使用相对路径。
     */
    public synchronized boolean cd(String path) throws IOException{
	String response = send("CWD " + path);
	return response.startsWith("250 ");
    }
    /**
     * @return 当前完整路径
     */
    public synchronized String pwd() throws IOException{
	String response = send("PWD");
	return response.substring(response.indexOf('"')+1, response.lastIndexOf('"'));
    }
    /**
     * 显示当前目录下的文件及目录列表
     * @return 数组。每项为一个文件/目录的描述
     */
    public synchronized String[] ls() throws IOException{
	return ls(".");
    }
    /**
     * 显示指定路径path下的文件及目录列表
     * @return 数组。每项为一个文件/目录的描述
     */
    public synchronized String[] ls(String path) throws IOException{
	if (path.trim().equals("")) path = ".";
	String str = send("STAT " + path);
	if (!str.startsWith("211"))
	    return null;
	String value = "";
        str = read();
        while (!str.startsWith("211 ")){
	    value += str + "\n";
	    str = read();
        }
	value = value.replace("211-", "");
	return value.split("\n");
    }
    /**
     * 获取指定文件pathAndName的大小。
     * @return
     * 	-1:	pathAndName不合法（可能为文件夹或者不存在)
     * 	非负数:	文件大小之值
     */
    public synchronized long filesize(String pathAndName) throws IOException{
	String response = send("STAT " + pathAndName);
	if (!response.startsWith("211-"))
	    return -1;
	String value = read();
	response = read();
	if (!response.startsWith("211 "))	//信息项不止3行，参数不合法
	    return -1;
	//解析value
	String[] list = value.split(" +");
	return Integer.valueOf(list[4]).intValue();
    }
    /**
     * 间接调用四参数的download()方法进行传输。
     * 此方法通常用作单线程下载，零偏移（初始下载）.
     */
    public synchronized IOCell download(String remotePathAndName, String localPathAndName)
	throws IOException{
	long size = filesize(remotePathAndName);
	if (size <= 0)
	    return null;
	return download(remotePathAndName, localPathAndName, 0, size);
    }
    /**
     * 间接调用四参数的download()方法进行传输。
     * 此方法通常用作单线程下载，断点续传。
     */
    public synchronized IOCell download(String remotePathAndName, String localPathAndName,
	    long offset) throws IOException{
	long size = filesize(remotePathAndName);
	if (size <= 0 || offset < 0 || offset >= size)
	    return null;
	return download(remotePathAndName, localPathAndName, 0, size);
    }
    /**
     * 启动一个下载项。下载remotePathAndName的文件，并保存至localPathAndName。
     * 此方法通常用作多线程下载。
     * @param remotePathAndName 服务器上的文件。
     * @param localPathAndName 本地的文件名。
     * @note 参数中的路径可以为相对路径。
     * @return 一个文件传输描述单元
     */
    public synchronized IOCell download(String remotePathAndName, String localPathAndName,
	    		long offset, long size)	throws IOException{
	//建立与本FTP控制流相关的传输数据流
	IOCell cell = new IOCell(clone());
	//设置为上传，设置断点偏移量
	cell.toDownload(remotePathAndName, localPathAndName, offset, size);
	//设置自动重试次数为10次
	cell.setAutoTry(10);
	//传输线程启动
	cell.start();
	return cell;
    }

    /**
     * 其实是调用四参数的upload()方法。
     * 此方法通常用作单线程上传，零偏移传输文件
     */
    public synchronized IOCell upload(String remotePathAndName, String localPathAndName)
    	throws IOException{
	long size = new File(localPathAndName).length();
	if (size <= 0)
	    return null;
	return upload(remotePathAndName, localPathAndName, 0, size);
    }

    /**
     * 其实是调用四参数的upload()方法。
     * 此方法通常用作单线程上传，断点续传。
     */
    public synchronized IOCell upload(String remotePathAndName, String localPathAndName,
	    long offset) throws IOException{
    File file = new File(localPathAndName);
	long size = file.length();
	if (size <= 0 || offset < 0 || offset >= size)
	    return null;
    	return upload(remotePathAndName, localPathAndName, offset, size);
    }

    /**
     * 启动一个上传项。上传localPathAndName至remotePathAndName。情况与download()类似。
     * 此方法通常用作多线程上传
     * @param remotePathAndName 服务器上的文件。
     * @param localPathAndName 本地的文件名。
     * @note 参数中的路径可以为相对路径。
     * @return 一个文件传输描述单元
     */
    public synchronized IOCell upload(String remotePathAndName, String localPathAndName,
	    		long offset, long size)	throws IOException{
	//建立与本FTP控制流相关的传输数据流
	IOCell cell = new IOCell(clone());
	//设置为上传，设置断点偏移量
	cell.toUpload(remotePathAndName, localPathAndName, offset, size);
	//设置自动重试次数为10次
	cell.setAutoTry(10);
	//传输线程启动
	cell.start();
	return cell;
    }

    /**
     * 发送TCP级别的命令字至服务器
     * @return 服务器的响应字符串
     */
    public String send(String command) throws IOException{
	//清空以前未读的响应
	if (serverSocket == null)
            setSocket();
	    //throw new IOException("Not connected.");
	while (inFromServer.ready())
	    read();
	//发送指令
	if (debug) System.out.println("-> " + command);
	log += command + '\n';
	outToServer.writeBytes(command + '\n');
	outToServer.flush();
	//返回服务器的响应
	return read();
    }
    public String send(String command, boolean flag) throws IOException{
	//清空以前未读的响应
	if (serverSocket == null)
            setSocket();
	    //throw new IOException("Not connected.");
	while (inFromServer.ready())
	    read();
	//发送指令
	if (debug) System.out.println("-> " + command);
	log += command + '\n';
	outToServer.writeBytes(command + '\n');
	outToServer.flush();
	//返回服务器的响应
    if (flag == false) return "";
	return read();
    }
    /**
     * 读取一行服务器信息。（可能会阻塞）
     */
    String read() throws IOException{
	if (serverSocket == null)
            setSocket();
	    //throw new IOException("Not connected.");
	String str = inFromServer.readLine();
	log += str + '\n';
	if (debug) System.out.println("<- " + str);
	return str;
    }

    protected CoreFtp clone(){
        CoreFtp one = new CoreFtp(host, port, username, password);
        one.debug = debug;
        one.mode = mode;
        try{
            one.connect();
        }catch(IOException e){
        }
        return one;
    }
}

/*
关于CoreFtp类的一些代码示例。请在使用前用下面语句导入类
import ftp.*;

一个简单示例：

//建立一个到ftp.sjtu.edu.cn:21的FTP连接（默认情况下，匿名）
CoreFtp ftp = new CoreFtp("ftp.sjtu.edu.cn", 21);
//登录FTP
ftp.connect();
//显示/（根目录）的文件和目录列表
String[] filesList = ftp.ls("/");
//
//...这里是得到列表后的一些处理
//
//进入some_dir目录
if (ftp.cd("some_dir") == false)
	System.out.println("enter some_dir failed.");

//下载a_file_name文件，并保存为save_name（默认情况下单线程，多线程技术尚未成熟）
IOCell cell = ftp.download("a_file_name", "save_name");
//根据获得的传输描述单元来监测传输情况
while(cell.isFinished() == false){
	System.out.println(cell.getRate());
}
//断开连接
ftp.disconnect();

*/

